李守中

Linux 后台运行程序

Table of Contents

1. 前台任务与后台任务

前台任务 (foreground job) 会独占命令行窗口,只有运行完了或者手动中止,才能执行其他命令。

变成守护进程的第一步,就是把它改成 后台任务 (background job)。

mv a ../b & 只要在命令的尾部加上符号 & ,启动的进程就会成为后台任务。

让正在运行的前台任务变为后台任务,参考这个操作流程:

  1. mv a ../b 任务启动。
  2. Ctrl + z 任务挂起。
  3. jobs 查看所有任务。
  4. bg %1 把标号为 1 的任务放入后台。
  5. disown -h %1 把标号为 1 的任务交给系统后台。

后台任务有两个特点:

  1. 继承当前 session (对话) 的标准输出 (stdout) 和标准错误 (stderr)。因此,后台任务的所有输出依然会同步地在命令行下显示。
  2. 不再继承当前 session 的标准输入 (stdin)。你无法向这个任务输入指令了。如果它试图读取标准输入,就会暂停执行 (halt)。

后台任务与前台任务的本质区别只有一个: 是否继承标准输入。

所以,执行后台任务的同时,用户还可以输入其他命令。

2. SIGHUP 信号

Linux 系统有这样一个设计:

  1. 用户准备退出 session;
  2. 系统向该 session 发出 SIGHUP 信号;
  3. session 将 SIGHUP 信号发给所有子进程;
  4. 子进程收到 SIGHUP 信号后,自动退出。

因为前台任务收到了 SIGHUP 信号,所以会随着 session 的退出而退出。

后台任务是否也会收到 SIGHUP 信号由 Shell 的 huponexit 参数决定。

执行 shopt | grep huponexit 可以查看 huponexit 参数的值。

大多数 Linux 系统,这个参数默认关闭 (off)。因此,session 退出的时候,不会把 SIGHUP 信号发给后台任务。所以,一般来说,后台任务不会随着 session 一起退出。

3. disown 命令

通过后台任务启动守护进程并不保险,因为有的系统的 huponexit 参数可能是打开的 (on)。

更保险的方法是使用 disown 命令将指定任务从后台任务列表 (jobs 命令的返回结果) 之中移除。

一个后台任务只要不在这个列表之中,session 就肯定不会向它发出 SIGHUP 信号。

mv a ../b &
disown

执行上面的命令以后,server.js进程就被移出了后台任务列表。执行 jobs 命令验证,输出结果里面,不会有这个进程。

disown 的用法:

  1. disown -r 移出所有正在执行的后台任务;
  2. disown -a 移出所有后台任务;
  3. disown -h 不移出后台任务,但是让它们不会收到 SIGHUP 信号;
  4. disown %2 disown -h %2 根据jobId,移出指定的后台任务。

4. 标准 I/O

使用 disown 命令还有一个问题: 退出 session 后,如果后台进程与标准 I/O 有交互,它还是会挂。

var http = require('http');

http.createServer(function(req, res) {
    console.log('server starts...'); // 加入此行
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World');
}).listen(5000);

启动上面的脚本,然后再执行 disown 命令。

node server.js &
disown

接着,退出 session,访问 5000 端口,会发现连不上。

因为后台任务的标准 I/O 继承自当前 session,disown 命令并没有改变这一点。

一旦后台任务读写标准 I/O,就会发现它已经不存在了,所以就报错终止执行。

为了解决这个问题,需要对"后台任务"的标准 I/O 进行重定向。

node server.js > stdout.txt 2> stderr.txt < /dev/null &
disown

上面这样执行,基本上就没有问题了。

5. nohup 命令

还有比 disown 更方便的命令,就是 nohup 命令。

nohup node server.js &

nohup 命令对 server.js 进程做了三件事。

  1. 阻止 SIGHUP 信号发到这个进程。
  2. 关闭标准输入。该进程不再能够接收任何输入,即使运行在前台。
  3. 重定向标准输出和标准错误到文件 nohup.out。

也就是说,nohup 命令实际上将子进程与它所在的 session 分离了。

注意,nohup 命令不会自动把进程变为后台任务,所以必须加上 & 符号。

nohup 命令:

  1. 无论是否将 nohup 命令的输出重定向到终端,输出都将附加到当前目录的 nohup.out 文件中。
  2. 如果当前目录的 nohup.out 文件不可写,输出重定向到 ~/nohup.out 文件中。
  3. 如果没有文件能创建或打开以用于追加,那么 Command 参数指定的命令不可调用。

5.1. nohup 和 & 的区别

使用 nohup 运行程序:

  1. 无论是否将 nohup 命令的输出重定向到终端,输出都将附加到当前目录的 nohup.out 文件中。
  2. 使用 Ctrl + C 发送 SIGINT 信号,程序关闭
  3. 关闭 Shell Session 发送 SIGHUP 信号,程序免疫

使用 & 运行程序:

  1. 程序转入后台运行
  2. 结果会输出到终端
  3. 使用 Ctrl + C 发送 SIGINT 信号,程序不会停止
  4. 关闭 Shell session 发送 SIGHUP 信号,程序关闭

5.2. nohup 和 & 使用实例

一般两个一起组合使用不会受 Ctrl + C 和 Shell 关闭的影响:

nohup <command> & 最简单的后台运行。

nohup python main.py & 输出默认重定向到当前目录下 nohup.out 文件。

nohup python main.py >> main.log 2>&1 & 自定义输出文件 (标准输出和错误输出合并到 main.log)。

nohup python main.py &> main.log & 与上一个例子相同作用的简写方法。

nohup python main.py &> /dev/null & 不记录输出信息。

nohup python main.py &> /dev/null & echo $! > pidfile.txt 不记录输出信息并将程序的进程号写入 pidfile.txt 文件中,方便后续杀死进程。

使用 nohup 时,程序会自动将输出写入 nohup.out 文件中。如果程序不断向控制台输出,文件就会不停的变大。

如果不需要输出,可以用 /dev/null 解决这个问题。它相当于一个黑洞,任何输出到这个文件的东西都将消失。

nohup <command> >/dev/null 2>log & 只保留输出错误信息。

nohup <command> >/dev/null 2>&1 & 所有信息都不要。

2>&1 将错误信息重定向到标准输出。这使用到了 Linux 的重定向,其中 0 1 2 分别是标准输入、标准输出、标准错误输出,用来指定需要重定向的标准输入输出。默认情况下是标出输出,也就是 1

jobs -l 查看任务,返回任务编号和进程号。

bg %<jobnumber> 将一个在后台暂停的命令,变成在后台继续执行。如果后台中有多个命令,可以用 bg %<jobnumber> 将选中的命令调出。

fg %<jobnumber> 将后台中的命令调至前台继续运行。如果后台中有多个命令,可以用 fg %<jobnumber> (是命令编号,不是进程号) 将选中的命令调出。



Last Update: 2023-11-02 Thu 14:57

Generated by: Emacs 28.2 (Org mode 9.5.5)   Contact: [email protected]

若正文中无特殊说明,本站内容遵循: 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议